package com.zhangdi.demo.jdbc;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.base.Preconditions;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;

import java.util.Collection;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @author zhangdi
 * @version 1.0
 * @date 2022.6.30 17:30
 */

public class ConsistentHashWithVirtualNodeAlgorithm extends AbstractConsistentHashAlgorithm {
    protected final String SHARDING_COUNT_KEY = "sharding-virtual-count";
    protected final String SHARDING_DBS_KEY = "sharding-dbs";
    protected final String SHARDING_IPS_KEY = "sharding-ips";
    protected int virtualCount;
    protected int p = 16777619;
    protected int hash = (int) 2166136261L;
    protected final SortedMap<Long, String> virtualServers = new TreeMap<>();

    @Override
    public String getType() {
        return "CONSISTENT_HASH_DB";
    }

    @Override
    public void init(Properties props) {
        this.props = props;
        Preconditions.checkArgument(this.props.containsKey(SHARDING_COUNT_KEY), "Sharding virtual count cannot be null.");
        this.virtualCount = Integer.parseInt(this.props.get(SHARDING_COUNT_KEY).toString());
        this.initVirtualServers();
    }

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        long idHashCode = hash(preciseShardingValue.getValue());
        SortedMap<Long, String> hitServerMap = virtualServers.tailMap(idHashCode);
        if (hitServerMap.isEmpty()) {
            return virtualServers.get(virtualServers.firstKey());
        } else {
            return virtualServers.get(hitServerMap.firstKey());
        }

//        String targetName = preciseShardingValue.getDataNodeInfo().getPrefix() + preciseShardingValue.getValue() % collection.size();
//        for (String item : collection) {
//            if (item.equals(targetName)) {
//                return item;
//            }
//        }
    }

    private void initVirtualServers() {
        Preconditions.checkArgument(this.props.containsKey(SHARDING_DBS_KEY), "Sharding dbs cannot be null.");
        String shardingDBS = this.props.get(SHARDING_DBS_KEY).toString();
        String[] dbs = shardingDBS.split(",");

        Preconditions.checkArgument(this.props.containsKey(SHARDING_IPS_KEY), "Sharding ips cannot be null.");
        String shardingIPS = this.props.get(SHARDING_IPS_KEY).toString();

        if (StringUtils.isNotBlank(shardingIPS)) {
            String[] ips = shardingIPS.split(",");
            for (int i = 0; i < ips.length; i++) {
                long ipHash = hash(ips[i]);
                virtualServers.put(ipHash, dbs[i]);
                for (int n = 0; n < virtualCount; n++) {
                    long virHash = Math.abs((ips[i] + "#" + n).hashCode());
                    virtualServers.put(virHash, dbs[i]);
                }
            }
        }
    }

    private int hash(Object key) {
        return this.hash(key+"");
    }

    private int hash(String key) {
        for (int i = 0; i < key.length(); i++) {
            hash = (hash ^ key.charAt(i)) * p;
        }
        hash += hash << 13;
        hash ^= hash << 7;
        hash += hash << 3;
        hash ^= hash << 17;
        hash += hash << 5;
        if (hash < 0) {
            hash = Math.abs(hash);
        }
        return hash;
    }
}
